package org.chorus;

import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

import org.chorus.DisplayUtils.LayoutType;

import jwo.landserf.gui.GISFrame;
import jwo.landserf.gui.SimpleGISFrame;
import jwo.landserf.process.io.FileHandler;
import jwo.landserf.structure.RasterMap;
import processing.core.PApplet;
import processing.core.PConstants;
import processing.pdf.PGraphicsPDF;

//  **********************************************************************************
/** Sample application for importing and describing surface models.
 *  @author Jo Wood
 *  @version 1.3.2, 22nd March, 2013
 */
//  **********************************************************************************

/* This file is part of the Chorus river bed analysis software package. Chorus is free software:
 * you can redistribute it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * Chorus is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this
 * source code (see COPYING included with this source code). If not, see 
 * http://www.gnu.org/licenses/.
 */

@SuppressWarnings("serial")
public class ChorusApp extends PApplet
{
	// ------------------------ Object and class variables --------------------------

	private ScreenMessager messager;			// For screen display messages.
	private String errorMessage;				// Used for error reporting to screen.
	private Map<Modular,String[]> moduleList;	// Modules and all the files used by each one.
	private boolean isCreatingPDF;
	private String title;						// Title of report.
	private PropertyHandler props;				// Contains user-configured options.
	
	private static String propertiesFile;		// Name and location of Chorus properties file.
	
	private static final String VERSION = "1.3.2, 22nd March, 2013";
	
	// ------------------------------ Starter method --------------------------------

	/** Starts the sketch as an application.
	 *  @param args Command line arguments (ignored). 
	 */
	public static void main(String[] args)
	{   
		if (args.length > 0)
		{
			propertiesFile = args[0];
		}
		else
		{
			propertiesFile = "scripts/chorus.props";
		}
		PApplet.main(new String[] {"org.chorus.ChorusApp"});
	}

	// ---------------------------------- Methods ----------------------------------
	
	/** Initialises Chorus application.
	 */
	public void setup()
	{
		size(1200,750);
		//size(1600,950);
		smooth();
		DisplayUtils.setupFonts(this);

		isCreatingPDF = false;
		messager = new ScreenMessager(this);

		if (frame !=null)
		{
			frame.setTitle("Chorus "+VERSION);
		}
		
		// Get the list of modules and data files from the properties file.
		props = new PropertyHandler(propertiesFile,this);
		
		if (props.foundPropertiesFile() == false)
		{
			errorMessage = "No properties file found";
		}
		else
		{
			String baseDir = props.getBaseDir();
			title = props.getTitle();

			moduleList = props.getModules();

			for (Modular module : moduleList.keySet())
			{
				for (String fileName : moduleList.get(module))
				{
					if (fileName.equalsIgnoreCase("null"))
					{
						// Insert blank entry into module.
						module.addDEM(null);
					}
					else if (fileName.toLowerCase().startsWith("legend"))
					{
						// Insert a legend into the module.
						module.addDEM(null);
						String fileName2 = fileName.substring(6).trim();
						//RasterMap raster = LandSerfIO.readRaster(baseDir+fileName2+".srf");
						RasterMap raster = readRaster(baseDir, fileName2);
						if (raster == null)
						{
							System.err.println("Warning: Cannot read legend raster from "+baseDir+fileName2);
							messager.displayText("Warning: Cannot read legend raster from "+baseDir+fileName2, 10);
						}
						else
						{
							if (module instanceof DEMDisplay)
							{
								((DEMDisplay)module).setLegendRasterM(raster);
							}
						}
					}
					else
					{
						//RasterMap raster = LandSerfIO.readRaster(baseDir+fileName+".srf");
						RasterMap raster = readRaster(baseDir, fileName);
						if (raster == null)
						{
							System.err.println("Warning: Cannot read raster from "+baseDir+fileName);
							messager.displayText("Warning: Cannot read raster from "+baseDir+fileName, 10);
						}
						else
						{
							module.addDEM(raster);
						}
					}
				}
			}

			// Standardise height of y-axis and x-axis units for all 1d charts of the same type
			float maxVar = 0;
			float maxIncl = 0;
			float minElev = Float.MAX_VALUE;
			float maxElev = -Float.MAX_VALUE;
			float maxProb = 0;

			String units = props.getUnits();
			for (Modular module: moduleList.keySet())
			{
				if (module instanceof PDFGrapher)
				{
					minElev = Math.min(minElev,((PDFGrapher)module).getMinElev());
					maxElev = Math.max(maxElev,((PDFGrapher)module).getMaxElev());
					maxProb = Math.max(maxProb,((PDFGrapher)module).getMaxProb());
				}
				else if (module instanceof Variogram)
				{
					maxVar = Math.max(maxVar,((Variogram)module).getMaxVariance());
				}
				else if (module instanceof InclinationChart)
				{
					maxIncl = Math.max(maxIncl,
							           Math.max(Math.abs(((InclinationChart)module).getMaxInclination()),
									            Math.abs(((InclinationChart)module).getMinInclination())));
				}
			}

			for (Modular module: moduleList.keySet())
			{
				if (module instanceof PDFGrapher)
				{
					((PDFGrapher)module).setUnits(units);
					((PDFGrapher)module).setMaxProb(maxProb);
					((PDFGrapher)module).setMinElev(minElev);
					((PDFGrapher)module).setMaxElev(maxElev);
				}
				else if (module instanceof Variogram)
				{
					((Variogram)module).setUnits(units);
					((Variogram)module).setMaxVariance(maxVar);
				}
				else if (module instanceof InclinationChart)
				{
					((InclinationChart)module).setUnits(units);
					((InclinationChart)module).setMaxInclination(maxIncl);
					((InclinationChart)module).setMinInclination(-maxIncl);
				}
				else if (module instanceof Variogram2D)
				{
					((Variogram2D)module).setUnits(units);
				}
			}
		}
	}


	/** Gets each module to draw itself in the application.
	 */
	public void draw()
	{
		background(255);
		
		fill(50);
		textFont(DisplayUtils.largeFont);
		textSize(32);
		textAlign(PConstants.CENTER, PConstants.CENTER);
		
		if (moduleList != null)
		{
			text(title,width/2,12);
			
			if (moduleList.size() == 0)
			{
				textSize(48);
				text("No modules defined in properties file",width/2,height/2);
			}
			else
			{
				Rectangle2D.Float[] rects = DisplayUtils.getBoundaries(0, 30, width, height-30, moduleList.size(), LayoutType.GRID);

				int i=0;
				for (Modular module: moduleList.keySet())
				{
					module.draw(rects[i].x,rects[i].y,rects[i].width,rects[i].height);
					i++;
				}
			}

			// Display any screen messages if they have been requested.
			if (messager.isMessageDisplayed())
			{
				messager.draw(0,0,width,height);
			}
		}
		else
		{
			text(errorMessage,10,10,width-10,height-10);
		}

		// No need to redisplay unless some update has been requested.
		noLoop();
	}

	/** Gets each module to respond to mouse press events if required.
	 */
	public void mousePressed()
	{
		for (Modular module: moduleList.keySet())
		{
			module.mousePressed();
		}
		loop();		// Update display.
	}

	/** Gets each module to respond to mouse release events if required.
	 */
	public void mouseReleased()
	{
		for (Modular module: moduleList.keySet())
		{
			module.mouseReleased();
		}
		loop();		// Update display.
	}

	/** Gets each module to respond to mouse drag events if required.
	 */
	public void mouseDragged()
	{
		for (Modular module: moduleList.keySet())
		{
			module.mouseDragged();
		}
		loop();		// Update display.
	}

	/** Responds to keyboard input allowing help screens, printed output set to be generated.
	 */
	public void keyPressed()
	{		
		if ((key == 'p') || (key =='P'))
		{
			if (isCreatingPDF == false)
			{
				new PDFCreator(title).start();
			}
		}
		else if ((key == 'o') || (key == 'O'))
		{
			String baseDir = props.getBaseDir();
			
			int varCount=0;
			int pdfCount=0;
			int inclCount=0;
			for (Modular module: moduleList.keySet())
			{
				if (module instanceof Variogram)
				{
					boolean success = module.writeOutput(baseDir+"variogram"+varCount+".txt");
					if (success)
					{
						messager.displayText("Variogram data written to\n"+baseDir+"variogram"+varCount+".txt", 5);
					}
					else
					{
						messager.displayText("Problem writing variogram data", 5);
					}
					varCount++;
				}
				else if (module instanceof PDFGrapher)
				{
					boolean success = module.writeOutput(baseDir+"pdf"+pdfCount+".txt");
					if (success)
					{
						messager.displayText("PDF data written to\n"+baseDir+"pdf"+pdfCount+".txt", 5);
					}
					else
					{
						messager.displayText("Problem writing PDF data", 5);
					}
					
					// Write out summary statistics
					List<RasterSummary> summaries = ((PDFGrapher)module).getSummaries();
					try
					{
						PrintWriter output = new PrintWriter(baseDir+"stats"+pdfCount+".txt");
						
						for (RasterSummary summary : summaries)
						{
							output.println(summary);
						}
						output.flush();
						output.close();
						messager.displayText("DEM Summary statistics written to\n"+baseDir+"stats"+pdfCount+".txt", 5);
					}
					catch (IOException e)
					{
						messager.displayText("Problem writing summary statistics", 5);
					}
					pdfCount++;
				}
				else if (module instanceof InclinationChart)
				{
					boolean success = module.writeOutput(baseDir+"inclination"+inclCount+".txt");
					if (success)
					{
						messager.displayText("Inclination data written to\n"+baseDir+"inclination"+inclCount+".txt", 5);
					}
					else
					{
						messager.displayText("Problem writing inclination data", 5);
					}
					inclCount++;
				}
			}
		}
	}


	// ------------------------------------- Nested classes ------------------------------------- 
	
	private class PDFCreator extends Thread
	{
		private String pdfTitle;
		
		// ---------------- Constructor -------------------
		PDFCreator(String title)
		{
			this.pdfTitle = title;
		}
		
		// ------------------ Methods ---------------------
		
		/** Generates a PDF document containing output from each module.
		 */
		@SuppressWarnings("synthetic-access")
		@Override
		public void run()
		{
			boolean isLandscape = true;
			float margin = 72;
			int pixelWidth =isLandscape?839:595;		// For A4 output.
			isCreatingPDF = true;			

			DateFormat dateFormat = new SimpleDateFormat("MMM, yyyy HH:mm");
			Calendar now = Calendar.getInstance();
			int day = now.get(Calendar.DAY_OF_MONTH);

			String footerText = "Chorus Report generated "+day+DisplayUtils.getOrdinalSuffix(day)+" "+dateFormat.format(now.getTime());

			PGraphicsPDF pdf = (PGraphicsPDF)createGraphics(pixelWidth, Math.round(isLandscape?pixelWidth/1.41f:pixelWidth*1.41f), PDF, "report.pdf");
			pdf.beginDraw();

			pdf.background(255);
			pdf.textFont(DisplayUtils.mediumFont);

			pdf.fill(50);
			pdf.textAlign(PConstants.CENTER, PConstants.BOTTOM);
			pdf.text(pdfTitle,pdf.width/2,margin);

			pdf.textAlign(PConstants.RIGHT, PConstants.TOP);
			pdf.textFont(DisplayUtils.smallFont);
			pdf.text(footerText,pdf.width-margin,pdf.height-margin);
			
			// Display the first 4 modules on the first page
			Rectangle2D.Float[] rects = DisplayUtils.getBoundaries(margin, margin, pdf.width-2*margin, pdf.height-2*margin, 4,LayoutType.GRID);
			int i=0;
			int maxNumPerPage = 4;
			for (Modular module: moduleList.keySet())
			{
				if ((i > 0) && (i%maxNumPerPage == 0))
				{
					// Next page
					pdf.nextPage();
					rects = DisplayUtils.getBoundaries(margin, margin, pdf.width-2*margin, pdf.height-2*margin, 4,LayoutType.GRID);
					pdf.fill(0);
					pdf.textAlign(PConstants.RIGHT, PConstants.TOP);
					pdf.textFont(DisplayUtils.smallFont);
					pdf.text(footerText,pdf.width-margin,pdf.height-margin);
				}
				
				// Set output to the PDF file.
				module.setGraphics(pdf);
				module.draw(rects[i%maxNumPerPage].x,rects[i%maxNumPerPage].y,rects[i%maxNumPerPage].width,rects[i%maxNumPerPage].height);

				// Return normal output to the screen
				module.setGraphics(ChorusApp.this.g);
				
				i++;
			}

			pdf.dispose();
			pdf.endDraw();

			messager.displayText("PDF output created.", 3);
			isCreatingPDF = false;
		}
	}
	
	
	/** Reads in a raster file. Will attempt to use the extension of the raster to guess the file
	 *  format, or if no extension provided, will assume raster in LandSerf raster format.
	 *  @param baseDir Base directory in which file is located.
	 *  @param rastName Name of raster file to read. Can optionally have the file extension if format is to be specified.
	 *  @return Raster that has been read, or null if problem reading raster.
	 */
	private RasterMap readRaster(String baseDir,String rastName)
	{
		GISFrame gisFrame = new SimpleGISFrame();
		String rastBaseName = rastName;
		String extension = ".srf";
		int dotLocation = rastName.lastIndexOf(".");
		if (dotLocation >0)
		{
			// File extension provided.
			rastBaseName = rastBaseName.substring(0,dotLocation);
			extension = rastName.substring(dotLocation);
		}

		int rastType = FileHandler.guessFileType(rastBaseName+extension);
		return (RasterMap)FileHandler.readFile(baseDir+rastBaseName+extension, rastType, gisFrame, null);
	}
}
